import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;

import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
import com.gargoylesoftware.htmlunit.util.NameValuePair;

public final class FuzzInput {
	
	FuzzInput() {
	}

	public static void tryCommonLogin(HtmlTextInput usernameField, HtmlPasswordInput passwordField, 
			HtmlSubmitInput submitButton, HtmlForm form) {
		
		// don't have the web client print error messages since
		// we are guessing passwords
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(false);
		for (String username : Constants.commonUsernames) {
			for (String password : Constants.commonPasswords) {
				usernameField.setText(username);
				passwordField.setText(password);
				
				try {
					for (String method : Constants.formMethods) {
						form.setMethodAttribute(method);
						
						SecurityFuzzer.requestWait();
						
						HtmlPage page = submitButton.click();
						
						if(SecurityFuzzer.isSensitive){
							for(String value: SecurityFuzzer.sensitiveData){
								for(NameValuePair string: page.getWebResponse().getWebRequest().getRequestParameters()){
									if(string.getValue().equals(value)){
										SecurityLoggers.sensitiveDataLogger.log(Level.INFO, 
												String.format("Sensitive data discovered [%s] on page (%s)", value,page.getUrl().toString()));
									}
								}
							}
						}
						InputDiscovery.parseQueryStrings(page.getUrl().toString());
						if (isLoginSuccessful(page, createHtmlInputList(usernameField, passwordField, submitButton))) {
							SecurityLoggers.inputFuzzerLogger.log(Level.SEVERE, 
									String.format("Logged in on page (%s) " +
									"with common credentials: username=[%s], password=[%s]", 
									form.getPage().getUrl(), username, password));
						} else {
							if (page.getWebResponse().getStatusCode() != Constants.OK_STATUS_CODE &&
									page.getWebResponse().getStatusCode() != Constants.UNAUTHORIZED_STATUS_CODE) {
								SecurityLoggers.errorLogger.log(Level.SEVERE, 
										String.format("Common login response error on page (%s) " +
										"with input: username=[%s], password=[%s], form method=[%s]", 
										form.getPage().getUrl(), username, password, method));
							}
						}
					}
				} catch (IOException e) {
					// don't do anything, since we are guessing
					// common logins
				}
			}
		}
		
		// turn the web client's error printing back on
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(true);
		
	}
	
	public static void tryCommonURLQueries(String currentURL) {
		
		// don't have the web client print error messages since
		// we are guessing common URL queries
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(false);
		
		try {
			for (String commonQueryName : Constants.commonQueryNames) {
				for (String commonQueryValue : Constants.commonQueryValues) {
					Page pageWithCommonQuery = null;
					String urlWithCommonQuery = currentURL + "?" + commonQueryName + "=" + commonQueryValue;
					
					SecurityFuzzer.requestWait();
					
					pageWithCommonQuery = SecurityFuzzer.webClient.getPage(urlWithCommonQuery);
					
					if (pageWithCommonQuery != null) {
						if(SecurityFuzzer.isSensitive){
							for(String value: SecurityFuzzer.sensitiveData){
								for(NameValuePair string: pageWithCommonQuery.getWebResponse().getWebRequest().getRequestParameters()){
									if(string.getValue().equals(value)){
										SecurityLoggers.sensitiveDataLogger.log(Level.INFO, 
												String.format("Sensitive data discovered [%s] on page (%s)", value,pageWithCommonQuery.getUrl().toString()));
									}
								}
							}
						}
						InputDiscovery.parseQueryStrings(pageWithCommonQuery.getUrl().toString());
						SecurityLoggers.inputFuzzerLogger.log(Level.INFO, 
								String.format("Common query guessed on page (%s): query=%s, value=%s", 
										currentURL, commonQueryName, commonQueryValue));
					}
				}
			}
		} catch (FailingHttpStatusCodeException e) {
		} catch (MalformedURLException e) {
		} catch (IOException e) {
		} catch (Exception e) {
			// ignore failure to load web page with 
			// common queries; we are only guessing
		}
		
		// turn the web client's error logging back on
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(true);
		
	}
	
	public static void fuzzAuthentication(HtmlTextInput usernameField, HtmlPasswordInput passwordField, 
			HtmlSubmitInput submitButton, HtmlForm form) {
	
		// don't have the web client print error messages since
		// we are guessing passwords
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(false);
		
		for (String input : Constants.passiveSQLInjectionVector) {
			usernameField.setText(input);
			passwordField.setText(input);
			
			try {
				for (String method : Constants.formMethods) {
					form.setMethodAttribute(method);
					
					SecurityFuzzer.requestWait();
					
					HtmlPage page = submitButton.click();
					
					InputDiscovery.parseQueryStrings(page.getUrl().toString());
					
					
					if(SecurityFuzzer.isSensitive){
						for(String value: SecurityFuzzer.sensitiveData){
							for(NameValuePair string: page.getWebResponse().getWebRequest().getRequestParameters()){
								if(string.getValue().equals(value)){
									SecurityLoggers.sensitiveDataLogger.log(Level.INFO, 
											String.format("Sensitive data discovered [%s] on page (%s)", value,page.getUrl().toString()));
								}
							}
						}
					}
					if (isLoginSuccessful(page, createHtmlInputList(usernameField, passwordField, submitButton))) {
						SecurityLoggers.inputFuzzerLogger.log(Level.SEVERE, String.format("SQL Injection Vulnerability " +
								"on page (%s) with input: [%s]", form.getPage().getUrl(), input));
					} else {
						if (page.getWebResponse().getStatusCode() != Constants.OK_STATUS_CODE &&
								page.getWebResponse().getStatusCode() != Constants.UNAUTHORIZED_STATUS_CODE) {
							SecurityLoggers.errorLogger.log(Level.SEVERE, 
									String.format("Response error on page (%s) with input: " +
									"input=[%s], form method=[%s]", 
									form.getPage().getUrl(), input, method));
						}
					}
				}
			} catch (IOException e) {
				// don't do anything, since we are guessing
				// common logins
			}
			
		}
		
		// turn the web client's error printing back on
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(true);
		
	}
	
	public static void fuzzMultiPassword(HtmlPasswordInput oldPassword, HtmlPasswordInput newPassword, 
			HtmlSubmitInput submitButton, Collection<DomElement> otherElements, HtmlForm form) {
		
		Set<String> fuzzVectorNames = Completeness.getFuzzVectorCompletenessSet();
		for (String attackVectorName : fuzzVectorNames) {
			Set<String> fuzzAttackVector = Completeness.getAttackElementCompletenessSet(attackVectorName);
			for (String fuzzElement : fuzzAttackVector) {
				oldPassword.setText(fuzzElement);
				newPassword.setText(fuzzElement);
				setInputFieldValues(otherElements, fuzzElement);
				submitFuzzedRequest(submitButton, form, fuzzElement, attackVectorName);
			}
		}
		
	}
	
	public static void fuzzTextInputs(Collection<? extends DomElement> inputs, HtmlSubmitInput submitButton, HtmlForm form) {
		
		Set<String> fuzzVectorNames = Completeness.getFuzzVectorCompletenessSet();
		for (String attackVectorName : fuzzVectorNames) {
			Set<String> fuzzAttackVector = Completeness.getAttackElementCompletenessSet(attackVectorName);
			for (String fuzzElement : fuzzAttackVector) {
				setInputFieldValues(inputs, fuzzElement);
				submitFuzzedRequest(submitButton, form, fuzzElement, attackVectorName);
			}
		}
		
	}
	
	public static void fuzzQueryStrings(List<String> queryStrings, String url) {
		//HtmlPage rootPage = webClient.getPage(startingPage);
		HtmlPage page;
		url=url+"?";
		
		try {
			Set<String> fuzzVectorNames = Constants.fuzzVectorCollection.keySet();
			for (String attackVectorName : fuzzVectorNames) {
				Vector<String> fuzzAttackVector = Constants.fuzzVectorCollection.get(attackVectorName);
				for (String fuzzElement : fuzzAttackVector) {
					for(String query : queryStrings){
						url = url+query+"="+fuzzElement+"&";
					}
					url=url.substring(0, url.length());
					page=SecurityFuzzer.webClient.getPage(url);
				}
			}
		} catch (FailingHttpStatusCodeException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	private static boolean isLoginSuccessful(HtmlPage page, List<HtmlInput> inputs) {
		
		if (page.getWebResponse().getStatusCode() == Constants.UNAUTHORIZED_STATUS_CODE) {
			return false;
		}
		
		if (page.getWebResponse().getStatusCode() == Constants.OK_STATUS_CODE) {
			for (HtmlInput i : inputs) {
				if (!page.getElementsByName(i.getNameAttribute()).isEmpty()) {
					return false;
				}
			}
			
			return true;
		}
		
		return false;
		
	}
	
	private static List<HtmlInput> createHtmlInputList(HtmlInput ... args) {
		
		return Arrays.asList(args);
	
	}
	
	private static void setInputFieldValues(Collection<? extends DomElement> inputs, String fuzzElement) {
			
		for (DomElement input : inputs) {
			if (input instanceof HtmlTextInput) {
				HtmlTextInput textInput = (HtmlTextInput) input;
				textInput.setText(fuzzElement);
			} else if (input instanceof HtmlSubmitInput ||
					input instanceof HtmlHiddenInput) {
				// don't change the value
			} else {
				input.setAttribute("value", fuzzElement);
			}
		}
		
	}
	
	private static void submitFuzzedRequest(HtmlSubmitInput submitButton, HtmlForm form, 
			String fuzzElement, String fuzzVectorTestType) {
		
		try {
			for (String method : Constants.formMethods) {
				form.setMethodAttribute(method);
				
				SecurityFuzzer.requestWait();
				
				final HtmlPage page = submitButton.click();
				
				if(SecurityFuzzer.isSensitive){
					for(String value: SecurityFuzzer.sensitiveData){
						for(NameValuePair string: page.getWebResponse().getWebRequest().getRequestParameters()){
							if(string.getValue().equals(value)){
								SecurityLoggers.sensitiveDataLogger.log(Level.INFO, 
										String.format("Sensitive data discovered [%s] on page (%s)", value,page.getUrl().toString()));
							}
						}
					}
				}
				InputDiscovery.parseQueryStrings(page.getUrl().toString());
				if (page.getWebResponse().getStatusCode() != Constants.OK_STATUS_CODE) {
					SecurityLoggers.errorLogger.log(Level.SEVERE, 
							String.format(fuzzVectorTestType + " response error %d on page (%s) with: " +
							"input=[%s], form method=[%s]",
							page.getWebResponse().getStatusCode(), form.getPage().getUrl(), fuzzElement, method));	
				}
			}
		} catch (Exception e) {
			SecurityLoggers.errorLogger.log(Level.SEVERE, 
					String.format(fuzzVectorTestType + " exception on page (%s) with: input=[%s]",
							form.getPage().getUrl(), fuzzElement), 
					e);
		}
		
	}
	
}
